package com.util;

import org.jboss.netty.util.internal.ConcurrentHashMap;

import java.util.Map;
import java.util.Set;

/**
 * @author: sun.hongliang
 * @create: 2019/07/17 14:46
 */
public class LFUCache<K, V> {
    private final int maxCaptain;
    private final Map<K, HitRate> kHitRateMap;

    public LFUCache(int maxCaptain) {
        this.maxCaptain = maxCaptain;
        kHitRateMap = new ConcurrentHashMap<>();
    }

    public V put(K key, V value) {
        while (size() >= maxCaptain) {
            HitRate hitRate = findLastFrElements();
            if (hitRate == null) {
                break;
            }
            kHitRateMap.remove(hitRate.k);
        }
        HitRate hitRate = kHitRateMap.put(key, new HitRate(key, value, 1, now()));
        if (hitRate != null) {
            return hitRate.v;
        }
        return null;
    }

    public long now() {
        return System.currentTimeMillis();
    }

    private HitRate findLastFrElements() {
        HitRate lhr = null;
        Set<Map.Entry<K, HitRate>> entries = kHitRateMap.entrySet();
        for (Map.Entry<K, HitRate> entry : entries) {
            HitRate hitRate = entry.getValue();
            if (lhr == null) {
                lhr = hitRate;
                continue;
            }
            if (hitRate.hitCount < lhr.hitCount) {
                lhr = hitRate;
                continue;
            }
            if (hitRate.hitCount == lhr.hitCount && hitRate.lastTime < lhr.lastTime) {
                lhr = hitRate;
                continue;
            }
        }
        return lhr;
    }

    public int size() {
        return kHitRateMap.size();
    }

    public V get(K key) {
        HitRate hitRate = kHitRateMap.get(key);
        if (hitRate != null) {
            hitRate.hitCount += 1;
            hitRate.lastTime = now();
        }
        return hitRate.v;
    }

    private class HitRate {
        private K k;
        private V v;
        private int hitCount;
        private long lastTime;

        public HitRate(K k, V v, int hitCount, long lastTime) {
            this.k = k;
            this.v = v;
            this.hitCount = hitCount;
            this.lastTime = lastTime;
        }

        @Override
        public String toString() {
            return String.valueOf(v);
        }
    }

    @Override
    public String toString() {
        return kHitRateMap.toString();
    }
}
